package com.remark.service.impl;

import com.alibaba.fastjson.JSON;
import com.remark.search.pojo.RemarkImfo;
import com.remark.service.SearchService;
import com.sun.javafx.collections.MappingChange;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.GeoDistanceQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.GeoDistanceSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;

@Service
public class SearchServiceImpl implements SearchService {
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;
    //每页数据条数
    public final static Integer pageSize = 20;



    /**
     * 全文搜索
     *
     * @param searchMap
     * {"keywords"："查询文本"，"categoryName":"查询的分类","addressGPS":"precision,dimensionality"，"distance":"距离","Highlight":"是否高亮true(默认不高亮)","sortField","排序字段默认:score","sortRule":"排序方式，默认ASC，降序DESC"}
     * @return
     * @throws Exception
     */
    @Override
    public Map search(Map<String, String> searchMap) throws Exception {
        //默认数据
        //当前页面
         int page = 0;
        //默认排序字段
         String sortField = "score";
        //默认排序方法
         SortOrder sortRule = SortOrder.DESC;
        //默认距离
         Integer distance=5000;
        //构建结果Map
        HashMap<String, Object> resultMap = new HashMap<>();
        //有条件才查询 es
        if(searchMap!=null){
            //组合条件对象
            //BoolQueryBuilder用于多个查询条件组合
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            if (!StringUtils.isEmpty(searchMap.get("keywords"))){
                boolQuery.must(QueryBuilders.matchQuery("name",searchMap.get("keywords")).operator(Operator.OR));
            }
            //按照分类查询
            if (!StringUtils.isEmpty(searchMap.get("categoryName"))){
                boolQuery.filter(QueryBuilders.termQuery("categoryName",searchMap.get("categoryName")));
            }
            //地址范围
            if(!StringUtils.isEmpty(searchMap.get("distance"))){
                distance=Integer.parseInt(searchMap.get("distance"));
            }
            //按照地址区间过滤查询,默认百度地图天安门位置 39.9098071700,116.4041834900
            double latitude =39.9098071700;
            double longitude =116.4041834900;
            if(!StringUtils.isEmpty(searchMap.get("addressGPS"))){
                String[] addressGPS = searchMap.get("addressGPS").split(",");
                latitude = Double.parseDouble(addressGPS[0]);
                longitude = Double.parseDouble(addressGPS[1]);
                //以某点为中心搜索指定范围
                GeoDistanceQueryBuilder location = new GeoDistanceQueryBuilder("location");
                location.point(latitude, longitude);
                //定义范围，单位米
                location.distance(distance, DistanceUnit.METERS);
                boolQuery.filter(location);
            }

            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            nativeSearchQueryBuilder.withQuery(boolQuery);
            //高亮查询
            if("true".equals(searchMap.get(" Highlight"))){
                HighlightBuilder.Field field = new HighlightBuilder
                        .Field("name")
                        .preTags("<span style= 'color : red;' >")
                        .postTags("</span>");
                nativeSearchQueryBuilder.withHighlightFields(field);
            }

            //分页查询
            if (!StringUtils.isEmpty(searchMap.get("page"))){
                page = Integer.parseInt(searchMap.get("page"))-1;
            }
            nativeSearchQueryBuilder.withPageable(PageRequest.of(page,pageSize));
            //根据返回数据sortField排序
            if(!StringUtils.isEmpty(searchMap.get("sortField"))){
                sortField=searchMap.get("sortField");
                sortRule= SortOrder.ASC;
                if ("DESC".equals(searchMap.get("sortRule"))){
                    sortRule= SortOrder.DESC;
                }
            }

            if("location".equals(sortField)){
                //按地图距离排序
                GeoDistanceSortBuilder distanceSortBuilder =
                        new GeoDistanceSortBuilder("location", latitude, longitude);
                distanceSortBuilder.unit(DistanceUnit.METERS);
                distanceSortBuilder.order(sortRule);
                nativeSearchQueryBuilder.withSort(distanceSortBuilder);
            }else {
                //其他类型排序
                nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField).order(sortRule));
            }
            /**
             * 条件构建对象
             * 查询操作实体类
             * 查询结果操作对象
             */
            AggregatedPage<RemarkImfo> aggregatedPage = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), RemarkImfo.class, new SearchResultMapper() {



                @Override
                public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
                    //查询结果操作
                    ArrayList<T> list = new ArrayList<>();
                    //获取查询命中的数据
                    SearchHits hits = response.getHits();
                    if (hits!=null){
                        //有查询结果
                        for (SearchHit hit : hits) {
                            RemarkImfo remarkImfo = JSON.parseObject(hit.getSourceAsString(), RemarkImfo.class);
                            Map map = JSON.parseObject(hit.getSourceAsString(), Map.class);
                            Map locationMap = JSON.toJavaObject((JSON) map.get("location"), Map.class);
                            BigDecimal lat =   (BigDecimal) locationMap.get("lat");
                            BigDecimal lon = (BigDecimal) locationMap.get("lon");
                            GeoPoint location = new GeoPoint(lat.doubleValue(), lon.doubleValue());
                            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                            remarkImfo.setLocation(location);
                            if (highlightFields!=null&&highlightFields.size()>0){
                                remarkImfo.setName(highlightFields.get("name").getFragments()[0].toString());
                            }
                            list.add((T) remarkImfo);
                        }
                    }
                    return new AggregatedPageImpl<T>(list,pageable,hits.getTotalHits(),response.getAggregations());
                }
            });
            resultMap.put("total",aggregatedPage.getTotalElements());
            resultMap.put("totalPages",aggregatedPage.getTotalPages());

            List<RemarkImfo> content = aggregatedPage.getContent();
            for (RemarkImfo remarkImfo : content) {
                //有定位位数据获得距离
                if (!StringUtils.isEmpty(searchMap.get("addressGPS"))){
                    GeoPoint location = remarkImfo.getLocation();
                    double dis = GeoDistance.ARC.calculate(location.getLat(), location.getLon(), latitude, longitude, DistanceUnit.METERS);
                    remarkImfo.setDistance(dis);
                }

            }
            resultMap.put("rows",content);
            return resultMap;
        }

        //封装最终的返回结果
        return null;
    }

}
